<?php
class ReClassify
{
	public $db,$metaFilePath,$sourcePath,$coreAttributes,$fields,$location,$id,$mapArray,$projectId,$remapArray;

	private function getRankArray() {
		return array('s' => 'scientificName', 'g' => 'genus', 'f' => 'family', 'o' => 'order', 'c' => 'class', 'p' => 'phylum', 'k' => 'kingdom');
	}
	private function getService() {
		return array('url' => ' http://ecat-dev.gbif.org/ws/usage/', 'paramArray' => array('rkey' => '', 'sort' => 'alpha', 'pagesize' => 1, 'showRanks' => 'kpcofg', 'rank' => '', 'q' => ''));
	}
	private function getDeLimiter() {
		$coreAttributes = $this->get('coreAttributes');
		switch($coreAttributes->fieldsTerminatedBy) {
			case '\t':
				$chr = "\t";
				break;
			case ',':
			default:
				$chr = ",";
				break;
		}
		return $chr;
	}
	private function getTypesArray() {
		$typesArray = array(
			  'kingdom' => 'http://rs.tdwg.org/dwc/terms/kingdom'
			, 'phylum' => 'http://rs.tdwg.org/dwc/terms/phylum'
			, 'class' => 'http://rs.tdwg.org/dwc/terms/class'
			, 'order' => 'http://rs.tdwg.org/dwc/terms/order'
			, 'family' => 'http://rs.tdwg.org/dwc/terms/family'
			, 'genus' => 'http://rs.tdwg.org/dwc/terms/genus'
//			, 'specificEpithet' => 'http://rs.tdwg.org/dwc/terms/specificEpithet'
//			, 'infraspecificEpithet' => 'http://rs.tdwg.org/dwc/terms/infraspecificEpithet'
			, 'scientificName' => 'http://rs.tdwg.org/dwc/terms/scientificName'
			, 'scientificNameAuthorship' => 'http://rs.tdwg.org/dwc/terms/scientificNameAuthorship'
			);
		return $typesArray;
	}

	private function getDbMapArray() {
		# Gives the order-mapping by which the data is mapped in each project table.
		return array(
				  2 => 'kingdom'
				, 4 => 'phylum'
				, 6 => 'class'
				, 8 => 'order'
				, 10 => 'family'
				, 12 => 'genus'
				, 14 => 'scientificName'
				, 16 => 'scientificNameAuthorship'
				);
	}

	private function extractFromZipFile($zipFile,$extractFileName,$extractFilePath) {
		if(!file_exists($zipFile)) {$this->set('errorCode',101);return false;}
		$zip = new ZipArchive;
		if ($zip->open($zipFile) !== TRUE) { $this->set('errorCode',101);return false;}
		$index =  $zip->locateName($extractFileName, ZIPARCHIVE::FL_NOCASE|ZIPARCHIVE::FL_NODIR);
		$extractFile = $zip->getNameIndex($index);
		$pth = 'zip://'.$zipFile.'#' . $extractFile;
		copy($pth, $extractFilePath . basename($extractFile));
		$zip->close();
		return true;
	}
	private function rewriteZipFile($zipFile,$fileName,$replacementFile) {
		if(!file_exists($zipFile)) {$this->set('errorCode',101);return false;}
		if(!file_exists($replacementFile)) {return false;}
		$zip = new ZipArchive;
		if ($zip->open($zipFile) !== TRUE) { $this->set('errorCode',101);return false;}
		$index =  $zip->locateName($fileName, ZIPARCHIVE::FL_NOCASE|ZIPARCHIVE::FL_NODIR);
		$fileName = $zip->getNameIndex($index);
		if($zip->deleteName($fileName)) {
			if($zip->addFile($replacementFile,$fileName)) {
				$zip->close();
				return true;
			}
		}
		$zip->close();
		return false;
	}
	private function isFieldPresent($term) {
		$index = array_search($term,$this->get('fields'));
		return (false === $index) ? false : $index;
	}
	public function __construct(){
	}
	public function getError($errorCode) {
		$errorsArray = array(
				101 => 'Zip File does not exist'
			, 102 => 'Failed to Open Zip File.'
			, 103 => 'Core File does not exist.'
		);
		return $errorsArray[$errorCode];
	}
	public function get($property) {
		return (isset($this->{$property})) ? $this->{$property} : false;
	}
	public function set($property,$value) {
		$this->{$property} = $value;
		return true;
	}
	public function extractMetaFile() {
		# extracting metafile
		$zipFile = $this->get('sourcePath');
		if(false == $zipFile) return false;
		$metaFilePath = $this->get('metaFilePath');
		if($metaFilePath == '') {
			$metaFilePath =  sys_get_temp_dir() . '/';
			$this->set('metaFilePath',$metaFilePath);
		}
		if(false == $this->extractFromZipFile($zipFile, 'meta.xml', $metaFilePath)) {
			return(false);
#			exit($this->getError($this->get('errorCode')));
		}
		return true;
	}
	public function getMetaFile() {
		# getting data from metafile
		$metaFile = $this->get('metaFilePath') . 'meta.xml';
		if(!@file_exists($metaFile)) {
			$this->extractMetaFile();
		}
		clearstatcache();
		$metaObj = json_decode(@str_replace("@",'',xml2json::transformXmlStringToJson(@file_get_contents($metaFile))));
		$this->set('coreAttributes',$metaObj->archive->core->attributes);
		$this->set('location',$metaObj->archive->core->files->location);
		$fields = array();		
		$flds = $metaObj->archive->core->field;
		if(is_array($flds) && count($flds)) {
			foreach($flds as $fld) {
				$fields[$fld->attributes->index] = $fld->attributes->term;
			}
		} elseif (is_object($flds)) {
			$fields[$flds->attributes->index] = $flds->attributes->term;
		}

		$this->set('fields',$fields);
		$this->set('id',$metaObj->archive->core->id->attributes->index);
		# creating mapping array for fields
		$keys = array_keys($fields);
		sort($keys);
		$highestIndex = array_pop($keys);
		$mapArray = array();
		$remapArray = array(); # the types array values stored in the db in the order 2,4,6..,16
		$rankArray = $this->getRankArray();
		$count = 0;

		foreach($this->getTypesArray() as $name => $term) {
			if(false === ($index = array_search($term,$fields))) {
				$mapArray[++$highestIndex] = $term;
				$mapIndex = $highestIndex;
			} else {
				$mapIndex = $index;
			}

			if(in_array($name,$rankArray)) {
				$remapArray[$mapIndex] = $count++;
			}
		}
		$this->set('mapArray',$mapArray);
		$this->set('remapArray',$remapArray);

		return true;
	}

	public function processCoreFile() {
		# extracting corefile
		$tooLarge = false;
		$zipFile = $this->get('sourcePath');
		if(false == $zipFile) return false;
		$coreFileName = $this->get('location');
		if(false == $coreFileName) return false;
		$coreFilePath = $this->get('metaFilePath');
		if(false == $this->extractFromZipFile($zipFile,$coreFileName,$coreFilePath)) {return false;}
		$coreFile = $coreFilePath . $coreFileName;
		if(!file_exists($coreFile)){return false;}
		$indexArray = array();
		# creating table
		$tableQuery = ' CREATE TABLE IF NOT EXISTS `' . $this->get('projectId') . '` ( `recordId` int(11) NOT NULL AUTO_INCREMENT, ';
  		$tempArray = array();
  		$keyArray = array();
		foreach($this->getTypesArray() as $type => $term) {
			$tempArray[] = " {$type}_o varchar(60) DEFAULT NULL ";
			$tempArray[] = " {$type}_p varchar(60) DEFAULT NULL ";
			$keyArray[] = " KEY `{$type}` (`{$type}_o`)";
		}
		$tempArray[] = " `taxonAccordingTo` varchar(60) DEFAULT NULL ";
		$tempArray[] = " `classified` TINYINT NOT NULL DEFAULT '0' ";
		$tableQuery .= @implode(',',$tempArray);
		$tableQuery .= ' , PRIMARY KEY (`recordID`) , ' . @implode(', ',$keyArray) . ' ) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ';
		$this->db->query($tableQuery);
		# creating tab limited txt file to populate

		$this->lineCount = exec('perl -pe \'s/\r\n|\n|\r/\n/g\' ' . escapeshellarg($coreFile) . ' | wc -l');
		if ($this->lineCount > 100000) {
			# Too many rows
			$tooLarge = true;
		} else {
			# Less then max number of rows;
			if ( (($csvHandle = fopen($coreFile, 'r'))!== FALSE) && (($txtHandle = fopen($coreFilePath . $this->get('projectId') . '.txt', 'w')) !== FALSE) ) {
				$count = 0;$i=0;$autoincrement=0;
				$chr = $this->getDeLimiter();
				while (($data = fgetcsv($csvHandle, 1000, $chr )) !== FALSE) {
					if ($this->coreAttributes->ignoreHeaderLines) {
						if(++$i <=1 ) continue; # escaping the header row
					}
					$populateArray = array();
					$populateArray[] = 'NULL';
					foreach($this->getTypesArray() as $type => $term) {
						if(false != ($index = $this->isFieldPresent($term))) {
							$populateArray[] = (trim($data[$index]) == '') ? 'NULL' : trim($data[$index]);
						} else {
							$populateArray[] = 'NULL';
						}
						$populateArray[] = 'NULL'; # for _p field
					}
				
					fwrite($txtHandle,implode("\t", $populateArray) . "\n");
				}
				fclose($csvHandle);
				fclose($txtHandle);
			}
		}

		# populating the db
		if (!$tooLarge) {
			$importQuery = sprintf("LOAD DATA LOCAL INFILE '%s' INTO TABLE `%s`;", addslashes($coreFilePath) . $this->get('projectId') . '.txt',$this->get('projectId'));
			$this->db->query($importQuery);
			unlink($coreFilePath . $this->get('projectId') . '.txt');
			unlink($coreFile);
			return true;
		} else {
			unlink($coreFilePath . $this->get('projectId') . '.txt');
			unlink($coreFile);
			return false;
		}

	}

	public function classify($projectId, $quickMode = false) {
		$project = new Projects();
		$project->db = $this->db;
		if(!$project->loadByProjectId($projectId)) {return false;}
		$pAuth = $project->get('pAuth');
		$pAuthText = $project->get('pAuthText');
		$sAuth = $project->get('sAuth');
		$sAuthText = $project->get('sAuthText');
		$setAuthor = $project->get('setAuthor');
		$setTaxa = $project->get('setTaxa');		
		$rankArray = $this->getRankArray();
		$query = sprintf(" SELECT * FROM `%s` ", $projectId);
		if ($setTaxa && $quickMode) {
			$query .= ' WHERE family_o != "" GROUP BY `family_o` ';
			# removing scientific name and genus
			@array_shift($rankArray);
			@array_shift($rankArray);
		} else {
			$query .= ' WHERE scientificName_o != "" GROUP BY `scientificName_o` ';
		}

		$service = $this->getService();
		$ret = $this->db->query($query);

		if (is_object($ret)) {
			while ($row = $ret->fetch_object()) {
				$taxonAccordingTo = '';
				$lookUpFlag = true;
				$dataFlag = false;

				# Getting value to process
				$project->loadByRecord($row);
				$project->clearProcessedData();

				$paramArray = $service['paramArray'];
				$paramArray['rkey'] = $pAuth;
				
				if ($quickMode) {
					$paramArray['showRanks'] = 'kpco';
					$paramArray['rank'] = 'f';
					$paramArray['q'] = trim($row->family_o);
					$term = 'family_o';
				} else {
					$paramArray['showRanks'] = 'kpcofg';
					$paramArray['rank'] = 's';
					$paramArray['q'] = trim($row->scientificName_o);
					$term = 'scientificName_o';

					$tmp = trim($row->scientificName_o);
					$asci = ord($tmp[0]);

					if(!(($asci >= ord('a') && ($asci <= ord('z'))) || ($asci >= ord('A') && ($asci <= ord('Z'))))) {
						$lookUpFlag = false;
					}
				}
				if($lookUpFlag) {
					$jsonObject = CURL(trim($service['url']), $paramArray);
					$jsonObject = json_decode($jsonObject);
					$jsonObject = $jsonObject->data;
					if(!(is_array($jsonObject) && count($jsonObject))) {
						if($pAuth != $sAuth) {
							$paramArray['rkey'] = $sAuth;
							$jsonObject = CURL(trim($service['url']),$paramArray);
							$jsonObject = json_decode($jsonObject);
							$jsonObject = $jsonObject->data;
							if(is_array($jsonObject) && count($jsonObject)) {
								$dataFlag = true;
								$project->set('taxonAccordingTo', $sAuthText);
							}
						}
					} else {
						$dataFlag = true;
						$project->set('taxonAccordingTo', $pAuthText);
					}
				}

				if($dataFlag) {
					$jsonObject = $jsonObject[0];
					if ($setTaxa) {
						$project->set('kingdom_p', $jsonObject->kingdom);
						$project->set('phylum_p', $jsonObject->phylum);
						$project->set('class_p', $jsonObject->class);
						$project->set('order_p', $jsonObject->order);
						if (!$quickMode) {
							$project->set('family_p', $jsonObject->family);
							$project->set('genus_p', $jsonObject->genus);
						} else {
							$project->set('family_p', ucfirst($row->family_o));
						}
						$project->set('scientificName_p', ucfirst($row->scientificName_o));
					}

					if (!$quickMode) {
						switch($setAuthor) {
							case '1':
								$project->set('scientificNameAuthorship_p', trim(str_ireplace($paramArray['q'], "", $jsonObject->scientificName)) );
								break;
	
							case '2':
								if ($row->scientificNameAuthorship_o == '') {
									$project->set('scientificNameAuthorship_p', trim(str_ireplace($paramArray['q'], "", $jsonObject->scientificName)) );
								}
								break;
						}
					}
				}

				$project->set('classified', 1);
				$project->saveDynamicProjectTable($projectId, $term, $paramArray['q']);
			} # while

			# setting summary terms
			$qTplte1 = "SELECT COUNT(*) ct FROM `$projectId` WHERE `%s_o` != `%s_p`;";
			$qTplte2 = "SELECT COUNT(DISTINCT(%s_p)) ct FROM `$projectId`;";
			$project->loadByProjectId($projectId);
			foreach($this->getRankArray() as $trm) {
				$ret = $this->db->query_one(sprintf($qTplte1,$trm,$trm));
				$project->set($trm . '_variance', $ret->ct);
				$ret = $this->db->query_one(sprintf($qTplte2,$trm,$trm));
				$project->set($trm, $ret->ct);
			}
			$project->save();
		} # is object
		return true;
	}

	public function archive($projectId) {
		global $config;
		$project = new Projects;
		$project->db = &$this->db;
		if(!$project->loadByProjectId($projectId)) return false;
		$this->set('projectId',$projectId);
		$this->set('metaFilePath',$config['paths']['project'] . $projectId . '/');

		$zipFile = $this->get('metaFilePath') . $projectId . '.zip';

		$this->set('sourcePath',$zipFile);
		$this->getMetaFile();
		$chr = $this->getDeLimiter();

		if(false == $this->extractFromZipFile($zipFile,basename($this->get('location')),$this->get('metaFilePath'))) {return false;}

		$csvPath = $this->get('metaFilePath') . basename($this->get('location'));
		$newCsv = $this->get('metaFilePath') . $projectId . '.csv';

		$fpcsv = fopen($csvPath,'r');
		$fpnew = fopen($newCsv,'w');
		if(false !== $fpcsv && /*false !== $fptxt && */false !== $fpnew) {
			$coreAttributes = $this->get('coreAttributes');
			if(trim($coreAttributes->ignoreHeaderLines) == '1') {
				$mapArray = $this->get('mapArray');
				$data = fgetcsv($fpcsv, 1000,$chr);
				for($i=0;$i < count($data); $i++) {
					if(trim($data[$i]) == '') unset($data[$i]);
				}
				if(count($mapArray) && count($mapArray)) {
					foreach($mapArray as $heading) {
						$data[] = @basename($heading);
					}
				}
				# Adding the extra "taxonAccordingTo" header
				$data[] = 'taxonAccordingTo';
				fputcsv($fpnew,$data,$chr);
			}
			$ret = $project->getReMapData();
			if (is_object($ret)) {
				while (($data = fgetcsv($fpcsv, 1000, $chr)) !== FALSE) {
					$data1 = $ret->fetch_object();
					$taxonAccordingTo = $data1->taxonAccordingTo;
					if(is_object($data1)) {
						$data1 = array_values(get_object_vars($data1));
						$remapArray = $this->get('remapArray');
						if(count($remapArray) && is_array($remapArray)) {
							foreach($remapArray as $index => &$mp) {
								$mp = $data1[$mp];
							}
							foreach($remapArray as $index => $term) {
								if(!in_array($term,array('NULL',''))) {
									$data[$index] = $term;
								}
							}
						}
					}
					# Adding the extra "taxonAccordingTo"
					$data[] = $taxonAccordingTo;
					fputcsv($fpnew,$data,$chr);
				} # while
			}
			fclose($fpnew);
			fclose($fpcsv);
#			fclose($fptxt);
		}

		$metaFile = $this->get('metaFilePath') . 'meta.xml';
		$xml = new SimpleXMLElement($metaFile, NULL, TRUE);
		$core = $xml->core;
		$mapArray = $this->get('mapArray');
		if(is_array($mapArray) && count($mapArray)) {
			foreach($mapArray as $index => $term) {
				$field = $core->addChild('field');
				$field->addAttribute('index', $index);
				$field->addAttribute('term', $term);
			}
		}
		# Adding the extra "taxonAccordingTo"
		$field = $core->addChild('field');
		$field->addAttribute('index', ++$index);
		$field->addAttribute('term', 'taxonAccordingTo');
		$xml->asXML($metaFile);


		$zipPath = $this->get('metaFilePath') . $projectId . '.zip';
		$this->rewriteZipFile($zipPath,basename($this->get('location')), $newCsv);
		$this->rewriteZipFile($zipPath, 'meta.xml', $metaFile);
		unlink($newCsv);
		unlink($csvPath);
		return true;
	}

}
?>